/*
 * Copyright (C) 2009-2010 Geometer Plus <contact@geometerplus.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.geometerplus.zlibrary.text.view;

import org.geometerplus.zlibrary.core.view.ZLPaintContext;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public class ZLTextHyperlinkArea {
    final ZLTextHyperlink Hyperlink;
    private final List<ZLTextElementArea> myList;
    private final int myFromIndex;
    private int myToIndex;
    private HorizontalConvexHull myHull;

    ZLTextHyperlinkArea(ZLTextHyperlink hyperlink, List<ZLTextElementArea> list, int fromIndex) {
        Hyperlink = hyperlink;
        myList = list;
        myFromIndex = fromIndex;
        myToIndex = fromIndex + 1;
    }

    void extend() {
        ++myToIndex;
    }

    public List<ZLTextElementArea> textAreas() {
        return myList.subList(myFromIndex, myToIndex);
    }

    public void draw(ZLPaintContext context) {
        if (myHull == null) {
            myHull = new HorizontalConvexHull();
        }
        myHull.draw(context);
    }

    public int distanceTo(int x, int y) {
        if (myHull == null) {
            myHull = new HorizontalConvexHull();
        }
        return myHull.distanceTo(x, y);
    }

    public boolean equals(Object other) {
        if (!(other instanceof ZLTextHyperlinkArea)) {
            return false;
        }
        return Hyperlink == ((ZLTextHyperlinkArea) other).Hyperlink;
    }

    private final class HorizontalConvexHull {
        private final LinkedList<Rectangle> myRectangles = new LinkedList<Rectangle>();

        HorizontalConvexHull() {
            for (ZLTextElementArea area : textAreas()) {
                addArea(area);
            }
            normalize();
        }

        private void addArea(ZLTextElementArea area) {
            if (myRectangles.isEmpty()) {
                myRectangles.add(new Rectangle(area.XStart, area.XEnd, area.YStart, area.YEnd));
                return;
            }
            final int top = area.YStart;
            final int bottom = area.YEnd;
            for (ListIterator<Rectangle> iter = myRectangles.listIterator(); iter.hasNext();) {
                Rectangle r = iter.next();
                if (r.Bottom <= top) {
                    continue;
                }
                if (r.Top >= bottom) {
                    break;
                }
                if (r.Top < top) {
                    final Rectangle before = new Rectangle(r);
                    before.Bottom = top;
                    r.Top = top;
                    iter.previous();
                    iter.add(before);
                    iter.next();
                }
                if (r.Bottom > bottom) {
                    final Rectangle after = new Rectangle(r);
                    after.Top = bottom;
                    r.Bottom = bottom;
                    iter.add(after);
                }
                r.Left = Math.min(r.Left, area.XStart);
                r.Right = Math.max(r.Right, area.XEnd);
            }

            final Rectangle first = myRectangles.getFirst();
            if (top < first.Top) {
                myRectangles.add(0, new Rectangle(area.XStart, area.XEnd, top, Math.min(bottom, first.Top)));
            }

            final Rectangle last = myRectangles.getLast();
            if (bottom > last.Bottom) {
                myRectangles.add(new Rectangle(area.XStart, area.XEnd, Math.max(top, last.Bottom), bottom));
            }
        }

        private void normalize() {
            Rectangle previous = null;
            for (ListIterator<Rectangle> iter = myRectangles.listIterator(); iter.hasNext();) {
                final Rectangle current = iter.next();
                if (previous != null) {
                    if ((previous.Left == current.Left) && (previous.Right == current.Right)) {
                        previous.Bottom = current.Bottom;
                        iter.remove();
                        continue;
                    }
                    if ((previous.Bottom != current.Top) &&
                            (current.Left <= previous.Right) &&
                            (previous.Left <= current.Right)) {
                        iter.previous();
                        iter.add(new Rectangle(
                                Math.max(previous.Left, current.Left),
                                Math.min(previous.Right, current.Right),
                                previous.Bottom,
                                current.Top
                        ));
                        iter.next();
                    }
                }
                previous = current;
            }
        }

        int distanceTo(int x, int y) {
            int distance = Integer.MAX_VALUE;
            for (Rectangle r : myRectangles) {
                final int xd = (r.Left > x) ? r.Left - x : ((r.Right < x) ? x - r.Right : 0);
                final int yd = (r.Top > y) ? r.Top - y : ((r.Bottom < y) ? y - r.Bottom : 0);
                distance = Math.min(distance, Math.max(xd, yd));
                if (distance == 0) {
                    break;
                }
            }
            return distance;
        }

        void draw(ZLPaintContext context) {
            final LinkedList<Rectangle> rectangles = new LinkedList<Rectangle>(myRectangles);
            while (!rectangles.isEmpty()) {
                final LinkedList<Rectangle> connected = new LinkedList<Rectangle>();
                Rectangle previous = null;
                for (final Iterator<Rectangle> iter = rectangles.iterator(); iter.hasNext();) {
                    final Rectangle current = iter.next();
                    if ((previous != null) &&
                            ((previous.Left > current.Right) || (current.Left > previous.Right))) {
                        break;
                    }
                    iter.remove();
                    connected.add(current);
                    previous = current;
                }

                final LinkedList<Integer> xList = new LinkedList<Integer>();
                final LinkedList<Integer> yList = new LinkedList<Integer>();
                int x = 0, xPrev = 0;

                final ListIterator<Rectangle> iter = connected.listIterator();
                Rectangle r = iter.next();
                x = r.Right + 2;
                xList.add(x);
                yList.add(r.Top);
                while (iter.hasNext()) {
                    xPrev = x;
                    r = iter.next();
                    x = r.Right + 2;
                    if (x != xPrev) {
                        final int y = (x < xPrev) ? r.Top + 2 : r.Top;
                        xList.add(xPrev);
                        yList.add(y);
                        xList.add(x);
                        yList.add(y);
                    }
                }
                xList.add(x);
                yList.add(r.Bottom + 2);

                r = iter.previous();
                x = r.Left - 2;
                xList.add(x);
                yList.add(r.Bottom + 2);
                while (iter.hasPrevious()) {
                    xPrev = x;
                    r = iter.previous();
                    x = r.Left - 2;
                    if (x != xPrev) {
                        final int y = (x > xPrev) ? r.Bottom : r.Bottom + 2;
                        xList.add(xPrev);
                        yList.add(y);
                        xList.add(x);
                        yList.add(y);
                    }
                }
                xList.add(x);
                yList.add(r.Top);

                final int xs[] = new int[xList.size()];
                final int ys[] = new int[yList.size()];
                int count = 0;
                for (int xx : xList) {
                    xs[count++] = xx;
                }
                count = 0;
                for (int yy : yList) {
                    ys[count++] = yy;
                }
                context.drawOutline(xs, ys);
            }
        }
    }

    private static final class Rectangle {
        int Left;
        int Right;
        int Top;
        int Bottom;

        Rectangle(int left, int right, int top, int bottom) {
            Left = left;
            Right = right;
            Top = top;
            Bottom = bottom;
        }

        Rectangle(Rectangle orig) {
            Left = orig.Left;
            Right = orig.Right;
            Top = orig.Top;
            Bottom = orig.Bottom;
        }
    }
}
